/*!
 * \file WtLMDB.hpp
 * \project WonderTrader
 *
 * \author Wesley
 * \date 2020/03/30
 * 
 * \brief LMDB数据库封装工具
 * 
 * \details 本文件提供了对LMDB（Lightning Memory-Mapped Database）的C++封装。
 *          主要功能包括：
 *          - LMDB数据库的创建和管理
 *          - 事务管理（读写事务、只读事务）
 *          - 键值对的存储和查询
 *          - 范围查询和游标操作
 *          - 异常安全的资源管理
 *          
 *          LMDB是一个高性能的嵌入式键值数据库，特点：
 *          - 内存映射文件，读取速度极快
 *          - ACID事务支持
 *          - 零拷贝读取
 *          - 多版本并发控制（MVCC）
 *          - 跨平台支持
 */
#pragma once
#include <stdint.h>
#include <string>
#include <functional>
#include <vector>
#include <algorithm>

#if _WIN32
#include <direct.h>
#include <io.h>
#else
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#endif

#include "../Includes/WTSMarcos.h"
#include "lmdb/lmdb.h"

NS_WTP_BEGIN

/*!< 值数组类型定义，用于批量查询结果 */
typedef std::vector<std::string> ValueArray;

/*!< LMDB查询回调函数类型定义，参数为键数组和值数组 */
typedef std::function<void(const ValueArray&, const ValueArray&)> LMDBQueryCallback;

/*!
 * \class WtLMDB
 * \brief LMDB数据库封装类
 * 
 * \details WtLMDB提供了对LMDB数据库的基本封装，包括数据库的创建、打开和管理。
 *          该类的主要特点：
 *          - 自动资源管理（RAII）
 *          - 支持只读和读写模式
 *          - 跨平台目录创建
 *          - 错误状态管理
 *          - 环境和数据库句柄管理
 *          
 *          使用场景：
 *          - 历史数据存储
 *          - 配置信息持久化
 *          - 缓存数据管理
 *          - 高性能键值存储
 * 
 * \note 该类负责LMDB环境的生命周期管理
 * \see WtLMDBQuery
 */
class WtLMDB
{
public:
	/*!
	 * \brief 构造函数
	 * 
	 * \details 初始化LMDB数据库对象，设置访问模式
	 * 
	 * \param bReadOnly 是否为只读模式，默认为false（读写模式）
	 */
	WtLMDB(bool bReadOnly = false)
		: _env(NULL)
		, _dbi(0)
		, _errno(0)
		, _readonly(bReadOnly)
	{}

	/*!
	 * \brief 析构函数
	 * 
	 * \details 自动清理LMDB资源，关闭数据库和环境
	 */
	~WtLMDB()
	{
		if (_dbi != 0)
			mdb_dbi_close(_env, _dbi);

		if (_env != NULL)
			mdb_env_close(_env);
	}

public:
	/*!< 获取LMDB环境句柄 */
	inline MDB_env* env() const{ return _env; }
	
	/*!< 获取数据库句柄 */
	inline MDB_dbi	dbi() const { return _dbi; }

	/*!
	 * \brief 更新数据库句柄
	 * 
	 * \details 在事务中打开数据库，如果已经打开则直接返回
	 * 
	 * \param txn 事务句柄
	 * \return MDB_dbi 数据库句柄
	 */
	inline MDB_dbi update_dbi(MDB_txn* txn)
	{
		if (_dbi != 0)
			return _dbi;

		_errno = mdb_dbi_open(txn, NULL, 0, &_dbi);
		return _dbi;
	}

	/*!
	 * \brief 打开LMDB数据库
	 * 
	 * \details 创建或打开指定路径的LMDB数据库。如果目录不存在会自动创建。
	 *          该方法会：
	 *          1. 检查目录是否存在，不存在则创建
	 *          2. 创建LMDB环境
	 *          3. 打开数据库文件
	 * 
	 * \param path 数据库文件路径
	 * \param mapsize 内存映射大小，默认8MB
	 * \return bool 是否成功打开
	 * 
	 * \note 目录不存在时会自动创建
	 */
	bool open(const char* path, std::size_t mapsize = 8*1024*1024)
	{
#if _MSC_VER
        int ret = _access(path, 0);
#else
        int ret = access(path, 0);
#endif
		if(ret != 0)
		{
			// 目录不存在，创建目录
#if _WIN32
			_mkdir(path);
#else
			mkdir(path, 777);
#endif
		}

		// 创建LMDB环境
		int _errno = mdb_env_create(&_env);
		if (_errno != MDB_SUCCESS)
			return false;

		// 打开数据库
		_errno = mdb_env_open(_env, path, 0, 0664);
		if (_errno != MDB_SUCCESS)
			return false;

		return true;
	}

	/*!< 更新错误码 */
	inline void update_errno(int error) { _errno = error; }

	/*!< 检查是否有错误 */
	inline bool has_error() const { return _errno != MDB_SUCCESS; }

	/*!< 检查是否为只读模式 */
	inline bool is_readonly() const { return _readonly; }

	/*!
	 * \brief 获取错误信息
	 * 
	 * \return const char* 错误信息字符串
	 */
	inline const char* errmsg()
	{
		return mdb_strerror(_errno);
	}

private:
	MDB_env*	_env;		/*!< LMDB环境句柄 */
	MDB_dbi		_dbi;		/*!< 数据库句柄 */
	int			_errno;		/*!< 错误码 */
	bool		_readonly;	/*!< 是否只读模式 */
};

/*!
 * \class WtLMDBQuery
 * \brief LMDB查询操作类
 * 
 * \details WtLMDBQuery提供了LMDB数据库的查询和操作接口，包括事务管理。
 *          该类的主要特点：
 *          - 自动事务管理（RAII）
 *          - 支持读写和只读事务
 *          - 键值对的增删改查
 *          - 范围查询和游标操作
 *          - 异常安全的事务提交/回滚
 *          
 *          查询类型：
 *          - 单键查询：根据键获取值
 *          - 范围查询：获取指定范围内的键值对
 *          - 前向查询：获取指定键之后的数据
 *          - 后向查询：获取指定键之前的数据
 *          - 全量查询：获取所有数据
 * 
 * \note 该类负责事务的生命周期管理
 * \see WtLMDB
 */
class WtLMDBQuery
{
public:
	/*!
	 * \brief 构造函数
	 * 
	 * \details 创建查询对象并开始事务。根据数据库的只读属性决定事务类型。
	 * 
	 * \param db LMDB数据库对象引用
	 */
	WtLMDBQuery(WtLMDB& db)
		: _txn(NULL)
		, _commited(false)
		, _db(db)
	{
		_readonly = db.is_readonly();
		_db.update_errno(mdb_txn_begin(_db.env(), NULL, (_readonly ? MDB_RDONLY : 0), &_txn));
		_dbi = _db.update_dbi(_txn);
	}
	
	/*!
	 * \brief 析构函数
	 * 
	 * \details 自动处理事务结束。只读事务会被中止，读写事务会被提交（如果未手动提交）。
	 */
	~WtLMDBQuery()
	{
		if (_readonly)
			mdb_txn_abort(_txn);
		else if(!_commited)
			_db.update_errno(mdb_txn_commit(_txn));
	}

public:
	/*!
	 * \brief 回滚事务
	 * 
	 * \details 中止当前事务，丢弃所有未提交的更改
	 */
	inline void	rollback()
	{
		if (_commited)
			return;

		mdb_txn_abort(_txn);
		_commited = true;
	}

	/*!
	 * \brief 提交事务
	 * 
	 * \details 提交当前事务，保存所有更改到数据库
	 */
	inline void	commit()
	{
		if (_commited || _readonly)
			return;

		_db.update_errno(mdb_txn_commit(_txn));
		_commited = true;
	}

	/*!
	 * \brief 存储键值对（字符串版本）
	 * 
	 * \param key 键字符串
	 * \param val 值字符串
	 * \return bool 是否成功存储
	 */
	bool put(const std::string& key, const std::string& val)
	{
		return put((void*)key.data(), key.size(), (void*)val.data(), val.size());
	}

	/*!
	 * \brief 存储键值对（原始数据版本）
	 * 
	 * \details 将指定的键值对存储到数据库中
	 * 
	 * \param key 键数据指针
	 * \param klen 键数据长度
	 * \param val 值数据指针
	 * \param vlen 值数据长度
	 * \return bool 是否成功存储
	 */
	bool put(void* key, std::size_t klen, void* val, std::size_t vlen)
	{
		MDB_val mKey, mData;
		mKey.mv_data = key;
		mKey.mv_size = klen;

		mData.mv_data = val;
		mData.mv_size = vlen;
		int _errno = mdb_put(_txn, _dbi, &mKey, &mData, 0);
		_db.update_errno(_errno);
		return (_errno == MDB_SUCCESS);
	}

	/*!
	 * \brief 获取指定键的值（字符串版本）
	 * 
	 * \param key 键字符串
	 * \return std::string 对应的值，不存在返回空字符串
	 */
	std::string get(const std::string& key)
	{
		return get((void*)key.data(), key.size());
	}

	/*!
	 * \brief 获取指定键的值（原始数据版本）
	 * 
	 * \details 根据键获取对应的值数据
	 * 
	 * \param key 键数据指针
	 * \param klen 键数据长度
	 * \return std::string 对应的值，不存在返回空字符串
	 */
	std::string get(void* key, std::size_t klen)
	{
		MDB_cursor* cursor;
		int _errno = mdb_cursor_open(_txn, _dbi, &cursor);
		_db.update_errno(_errno);
		if (_errno != MDB_SUCCESS)
			return std::move(std::string());

		MDB_val mKey, mData;
		mKey.mv_data = key;
		mKey.mv_size = klen;

		_errno = mdb_cursor_get(cursor, &mKey, &mData, MDB_NEXT);
		_db.update_errno(_errno);
		if (_errno != MDB_SUCCESS)
			return std::move(std::string());

		auto ret = std::string((char*)mData.mv_data, mData.mv_size);
		mdb_cursor_close(cursor);
		return std::move(ret);
	}

	/*!
	 * \brief 获取范围数据
	 * 
	 * \details 获取指定键范围内的所有键值对数据
	 * 
	 * \param lower_key 下边界键
	 * \param upper_key 上边界键
	 * \param cb 回调函数，用于处理查询结果
	 * \return int 查询到的记录数量
	 */
	int get_range(const std::string& lower_key, const std::string& upper_key, LMDBQueryCallback cb)
	{
		MDB_cursor* cursor;
		int _errno = mdb_cursor_open(_txn, _dbi, &cursor);
		_db.update_errno(_errno);
		if (_errno != MDB_SUCCESS)
			return 0;

		MDB_val lKey, rKey, mData;
		lKey.mv_data = (void*)lower_key.data();
		lKey.mv_size = lower_key.size();

		rKey.mv_data = (void*)upper_key.data();
		rKey.mv_size = upper_key.size();
		
		if (_errno != MDB_SUCCESS)
			return 0;

		int cnt = 0;
		MDB_cursor_op op = MDB_SET_RANGE;
		std::vector<std::string> ayKeys, ayVals;
		for(; (_errno = mdb_cursor_get(cursor, &lKey, &mData, op))==MDB_SUCCESS;)
		{
			_db.update_errno(_errno);
			if(_errno == MDB_NOTFOUND)
				break;

			if(memcmp(lKey.mv_data, rKey.mv_data, lKey.mv_size) > 0)
				break;

			// 收集数据
			ayKeys.emplace_back(std::string((char*)lKey.mv_data, lKey.mv_size));
			ayVals.emplace_back(std::string((char*)mData.mv_data, mData.mv_size));
			cnt++;
			op = MDB_NEXT;
		} 

		cb(ayKeys, ayVals);
		mdb_cursor_close(cursor);
		return cnt;
	}

	/*!
	 * \brief 获取upper_key之前的数据
	 * 
	 * \details 从upper_key开始向前查找，找到之后做一个reverse。
	 *          这个方法用于获取指定上边界之前的指定数量的记录。
	 * 
	 * \param lower_key 下边界，主要是做检查用，因为如果跨合约查找的话，可能会查到其他合约的数据
	 * \param upper_key 上边界
	 * \param count 目标数据条数
	 * \param cb 回调函数
	 * \return int 实际查询到的记录数量
	 */
	int get_lowers(const std::string& lower_key, const std::string& upper_key, int count, LMDBQueryCallback cb)
	{
		MDB_cursor* cursor;
		int _errno = mdb_cursor_open(_txn, _dbi, &cursor);
		_db.update_errno(_errno);
		if (_errno != MDB_SUCCESS)
			return 0;

		MDB_val rKey, mData;
		rKey.mv_data = (void*)upper_key.data();
		rKey.mv_size = upper_key.size();

		int cnt = 0;
		std::vector<std::string> ayKeys, ayVals;
		_errno = mdb_cursor_get(cursor, &rKey, &mData, MDB_SET_RANGE);
		_db.update_errno(_errno);

		if (_errno == MDB_NOTFOUND)
		{
			_errno = mdb_cursor_get(cursor, &rKey, &mData, MDB_LAST);
			_db.update_errno(_errno);
		}

		for (; _errno != MDB_NOTFOUND;)
		{
			// 如果当前查找到的key大于右边界，则直接向前退一步
			if (memcmp(rKey.mv_data, upper_key.data(), upper_key.size()) > 0)
			{
				_errno = mdb_cursor_get(cursor, &rKey, &mData, MDB_PREV);
				_db.update_errno(_errno);
				continue;
			}

			if (memcmp(rKey.mv_data, lower_key.data(), lower_key.size()) < 0)
				break;

			// 收集数据
			ayKeys.emplace_back(std::string((char*)rKey.mv_data, rKey.mv_size));
			ayVals.emplace_back(std::string((char*)mData.mv_data, mData.mv_size));
			cnt++;

			// 如果找到目标数量，则退出
			if(cnt == count)
				break;
			
			_errno = mdb_cursor_get(cursor, &rKey, &mData, MDB_PREV);
			_db.update_errno(_errno);
		}

		// 因为是向前查找的，所以需要做一个reverse
		std::reverse(ayKeys.begin(), ayKeys.end());
		std::reverse(ayVals.begin(), ayVals.end());
		cb(ayKeys, ayVals);
		mdb_cursor_close(cursor);
		return cnt;
	}

	/*!
	 * \brief 获取lower_key之后的数据
	 * 
	 * \details 从lower_key开始向后查找指定数量的记录。
	 * 
	 * \param lower_key 下边界
	 * \param upper_key 上边界，主要是做检查用，因为如果跨合约查找的话，可能会查到其他合约的数据
	 * \param count 目标数据条数
	 * \param cb 回调函数
	 * \return int 实际查询到的记录数量
	 */
	int get_uppers(const std::string& lower_key, const std::string& upper_key, int count, LMDBQueryCallback cb)
	{
		MDB_cursor* cursor;
		int _errno = mdb_cursor_open(_txn, _dbi, &cursor);
		if (_errno != MDB_SUCCESS)
			return 0;

		MDB_val bKey, mData;
		bKey.mv_data = (void*)lower_key.data();
		bKey.mv_size = lower_key.size();

		int cnt = 0;
		std::vector<std::string> ayKeys, ayVals;
		_errno = mdb_cursor_get(cursor, &bKey, &mData, MDB_SET_RANGE);
		_db.update_errno(_errno);
		for (; _errno != MDB_NOTFOUND;)
		{
			if (memcmp(bKey.mv_data, upper_key.data(), upper_key.size()) > 0)
				break;

			// 收集数据
			ayKeys.emplace_back(std::string((char*)bKey.mv_data, bKey.mv_size));
			ayVals.emplace_back(std::string((char*)mData.mv_data, mData.mv_size));
			cnt++;

			// 如果找到目标数量，则退出
			if (cnt == count)
				break;

			_errno = mdb_cursor_get(cursor, &bKey, &mData, MDB_NEXT);
			_db.update_errno(_errno);
		}

		cb(ayKeys, ayVals);
		mdb_cursor_close(cursor);
		return cnt;
	}

	/*!
	 * \brief 获取所有数据
	 * 
	 * \details 遍历数据库中的所有键值对
	 * 
	 * \param cb 回调函数，用于处理查询结果
	 * \return int 查询到的记录总数
	 */
	inline int get_all(LMDBQueryCallback cb)
	{
		MDB_cursor* cursor;
		int _errno = mdb_cursor_open(_txn, _dbi, &cursor);
		if (_errno != MDB_SUCCESS)
			return 0;

		MDB_val bKey, mData;
		std::vector<std::string> ayKeys, ayVals;
		for (; _errno != MDB_NOTFOUND;)
		{
			_errno = mdb_cursor_get(cursor, &bKey, &mData, MDB_NEXT);
			_db.update_errno(_errno);

			ayKeys.emplace_back(std::string((const char*)bKey.mv_data, bKey.mv_size));
			ayVals.emplace_back(std::string((const char*)mData.mv_data, mData.mv_size));
		}
		cb(ayKeys, ayVals);
		return (int)ayVals.size();
	}

private:
	WtLMDB&		_db;		/*!< LMDB数据库对象引用 */
	MDB_txn*	_txn;		/*!< 事务句柄 */
	MDB_dbi		_dbi;		/*!< 数据库句柄 */
	bool		_readonly;	/*!< 是否只读事务 */
	bool		_commited;	/*!< 是否已提交 */
};

NS_WTP_END
